﻿// Deep Earth is a community project available under the Microsoft Public License (Ms-PL)
// Code is provided as is and with no warranty – Use at your own risk
// View the project and the latest code at http://DeepEarth.codeplex.com/

using System;
using System.Windows;
using System.Windows.Input;

#if !WINDOWS_PHONE
using System.Reactive;
using System.Reactive.Linq;
#else
using Microsoft.Phone.Reactive;
#endif

namespace DeepEarth.Map.Core.Extension
{
    public static class UIElementEx
    {

#if !WINDOWS_PHONE
        public static IObservable<EventPattern<DragEventArgs>> GetDragEnter(this UIElement uiElement)
        {
            return Observable.FromEventPattern<DragEventArgs>(uiElement, "DragEnter");
        }

        public static IObservable<EventPattern<DragEventArgs>> GetDragLeave(this UIElement uiElement)
        {
            return Observable.FromEventPattern<DragEventArgs>(uiElement, "DragLeave");
        }

        public static IObservable<EventPattern<DragEventArgs>> GetDragOver(this UIElement uiElement)
        {
            return Observable.FromEventPattern<DragEventArgs>(uiElement, "DragOver");
        }

        public static IObservable<EventPattern<DragEventArgs>> GetDrop(this UIElement uiElement)
        {
            return Observable.FromEventPattern<DragEventArgs>(uiElement, "Drop");
        }

        public static IObservable<EventPattern<TextCompositionEventArgs>> GetTextInputUpdate(this UIElement uiElement)
        {
            return Observable.FromEventPattern<TextCompositionEventArgs>(uiElement, "TextInputUpdate");
        }

        public static IObservable<EventPattern<TextCompositionEventArgs>> GetTextInputStart(this UIElement uiElement)
        {
            return Observable.FromEventPattern<TextCompositionEventArgs>(uiElement, "TextInputStart");
        }

        public static IObservable<EventPattern<TextCompositionEventArgs>> GetTextInput(this UIElement uiElement)
        {
            return Observable.FromEventPattern<TextCompositionEventArgs>(uiElement, "TextInput");
        } 

        public static IObservable<EventPattern<ManipulationStartedEventArgs>> GetManipulationStarted(this UIElement uiElement)
        {
            return Observable.FromEventPattern<ManipulationStartedEventArgs>(uiElement, "ManipulationStarted");
        }

        public static IObservable<EventPattern<ManipulationDeltaEventArgs>> GetManipulationDelta(this UIElement uiElement)
        {
            return Observable.FromEventPattern<ManipulationDeltaEventArgs>(uiElement, "ManipulationDelta");
        }

        public static IObservable<EventPattern<ManipulationCompletedEventArgs>> GetManipulationCompleted(this UIElement uiElement)
        {
            return Observable.FromEventPattern<ManipulationCompletedEventArgs>(uiElement, "ManipulationCompleted");
        }

        public static IObservable<EventPattern<MouseEventArgs>> GetLostMouseCapture(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseEventArgs>(uiElement, "LostMouseCapture");
        }

        public static IObservable<EventPattern<RoutedEventArgs>> GetGotFocus(this UIElement uiElement)
        {
            return Observable.FromEventPattern<RoutedEventArgs>(uiElement, "GotFocus");
        }

        public static IObservable<EventPattern<RoutedEventArgs>> GetLostFocus(this UIElement uiElement)
        {
            return Observable.FromEventPattern<RoutedEventArgs>(uiElement, "LostFocus");
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetMouseLeftButtonDown(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseButtonEventArgs>(uiElement, "MouseLeftButtonDown");
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetMouseLeftButtonUp(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseButtonEventArgs>(uiElement, "MouseLeftButtonUp");
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetMouseRightButtonDown(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseButtonEventArgs>(uiElement, "MouseRightButtonDown");
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetMouseRightButtonUp(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseButtonEventArgs>(uiElement, "MouseRightButtonUp");
        }

        public static IObservable<EventPattern<KeyEventArgs>> GetKeyUp(this UIElement uiElement)
        {
            return Observable.FromEventPattern<KeyEventArgs>(uiElement, "KeyUp");
        }

        public static IObservable<EventPattern<KeyEventArgs>> GetKeyDown(this UIElement uiElement)
        {
            return Observable.FromEventPattern<KeyEventArgs>(uiElement, "KeyDown");
        }

        public static IObservable<EventPattern<MouseEventArgs>> GetMouseLeave(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseEventArgs>(uiElement, "MouseLeave");
        }

        public static IObservable<EventPattern<MouseEventArgs>> GetMouseEnter(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseEventArgs>(uiElement, "MouseEnter");
        }

        public static IObservable<EventPattern<MouseEventArgs>> GetMouseMove(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseEventArgs>(uiElement, "MouseMove");
        }

        public static IObservable<EventPattern<MouseWheelEventArgs>> GetMouseWheel(this UIElement uiElement)
        {
            return Observable.FromEventPattern<MouseWheelEventArgs>(uiElement, "MouseWheel");
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetRoutedMouseLeftButtonDown(this UIElement uiElement, bool handledEventsToo)
        {
            return Observable.FromEventPattern<MouseButtonEventArgs>(
                meh => uiElement.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(meh), handledEventsToo),
                meh => uiElement.RemoveHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(meh)));
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetRoutedMouseLeftButtonUp(this UIElement uiElement, bool handledEventsToo)
        {
            return Observable.FromEventPattern<MouseButtonEventArgs>(
                meh => uiElement.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(meh), handledEventsToo),
                meh => uiElement.RemoveHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(meh)));
        }

        // Custom Events
        public static IObservable<MapElementDragEventArgs> GetMouseDrag(this UIElement uiElement)
        {
            var move = GetMouseMove(uiElement)
                .SkipUntil(GetMouseLeftButtonDown(uiElement)).Do(o => uiElement.CaptureMouse())
                .TakeUntil(GetMouseLeftButtonUp(uiElement).Do(o => uiElement.ReleaseMouseCapture()));
            return move.Zip(move.Skip(1), (prev, cur) => new MapElementDragEventArgs
                    {
                        PixelDelta = new Point
                                        {
                                            X = cur.EventArgs.GetPosition(uiElement).X - prev.EventArgs.GetPosition(uiElement).X,
                                            Y = cur.EventArgs.GetPosition(uiElement).Y - prev.EventArgs.GetPosition(uiElement).Y
                                        }
                    }).Repeat();
        }

        public static IObservable<MapElementDragEventArgs> GetMouseDragWithDelayedCapture(this UIElement uiElement)
        {
            var move = GetMouseMove(uiElement)
                .SkipUntil(GetMouseLeftButtonDown(uiElement))
                .TakeUntil(GetMouseLeftButtonUp(uiElement).Do(o => uiElement.ReleaseMouseCapture()));
            return move.Zip(move.Skip(1), (prev, cur) => new MapElementDragEventArgs
                    {
                        PixelDelta = new Point
                                        {
                                            X = cur.EventArgs.GetPosition(uiElement).X - prev.EventArgs.GetPosition(uiElement).X,
                                            Y = cur.EventArgs.GetPosition(uiElement).Y - prev.EventArgs.GetPosition(uiElement).Y
                                        }
                    }).Do(o => uiElement.CaptureMouse()).Repeat();
        }

        public static IObservable<MapElementDragEventArgs> GetMouseDragWithCaptureCondition(this UIElement uiElement, Func<bool> condition)
        {
            var move = GetMouseMove(uiElement)
                .SkipUntil(GetMouseLeftButtonDown(uiElement)).Do(o => { if (condition()) uiElement.CaptureMouse(); })
                .TakeUntil(GetMouseLeftButtonUp(uiElement).Do(o => uiElement.ReleaseMouseCapture()));
            return move.Zip(move.Skip(1), (prev, cur) => new MapElementDragEventArgs
                    {
                        PixelDelta = new Point
                                        {
                                            X = cur.EventArgs.GetPosition(uiElement).X - prev.EventArgs.GetPosition(uiElement).X,
                                            Y = cur.EventArgs.GetPosition(uiElement).Y - prev.EventArgs.GetPosition(uiElement).Y
                                        }
                    }).Repeat();
        }

        public static IObservable<MapElementDragEventArgs> GetRoutedMouseDrag(this UIElement uiElement, bool handledEventsToo)
        {
            var move = GetMouseMove(uiElement)
                .SkipUntil(GetRoutedMouseLeftButtonDown(uiElement, handledEventsToo)).Do(o => uiElement.CaptureMouse())
                .TakeUntil(GetRoutedMouseLeftButtonUp(uiElement, handledEventsToo).Do(o => uiElement.ReleaseMouseCapture()));
            return move.Zip(move.Skip(1), (prev, cur) => new MapElementDragEventArgs
            {
                PixelDelta = new Point
                {
                    X = cur.EventArgs.GetPosition(uiElement).X - prev.EventArgs.GetPosition(uiElement).X,
                    Y = cur.EventArgs.GetPosition(uiElement).Y - prev.EventArgs.GetPosition(uiElement).Y
                }
            }).Repeat();
        }

        public static IObservable<MapElementDragEventArgs> GetLayerMouseDrag(this UIElement uiElement)
        {
            var move = GetMouseMove(uiElement)
                .SkipUntil(GetMouseLeftButtonDown(uiElement).Do(d => d.EventArgs.Handled = true))
                .TakeUntil(GetMouseLeftButtonUp(uiElement));
            return move.Zip(move.Skip(1), (prev, cur) => new MapElementDragEventArgs
            {
                PixelDelta = new Point
                {
                    X = cur.EventArgs.GetPosition(uiElement).X - prev.EventArgs.GetPosition(uiElement).X,
                    Y = cur.EventArgs.GetPosition(uiElement).Y - prev.EventArgs.GetPosition(uiElement).Y
                }
            }).Repeat();
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetMouseClick(this UIElement uiElement)
        {
            var mbu = GetMouseLeftButtonUp(uiElement);
            var mbd = GetMouseLeftButtonDown(uiElement);
            var mm = GetMouseMove(uiElement);

            return
                from u in mbu.SkipUntil(mbd).TakeUntil(mm).Repeat()
                select u;
        }

        public static IObservable<EventPattern<MouseButtonEventArgs>> GetRoutedMouseClick(this UIElement uiElement, bool handledEventsToo)
        {
            var mbu = GetRoutedMouseLeftButtonUp(uiElement, handledEventsToo);
            var mbd = GetRoutedMouseLeftButtonDown(uiElement, handledEventsToo);
            var mm = GetMouseMove(uiElement);

            return
                from u in mbu.SkipUntil(mbd).TakeUntil(mm).Repeat()
                select u;
        }

        public static IObservable<TimeInterval<EventPattern<MouseButtonEventArgs>>> GetDoubleClick(this UIElement uiElement)
        {
            return from dc in GetMouseClick(uiElement).TimeInterval()
                   .Where(click => click.Interval.TotalMilliseconds <= 250)
                   select dc;
        }

#else

        public static IObservable<IEvent<MouseButtonEventArgs>> GetMouseLeftButtonDown(this UIElement uiElement)
        {
            return Observable.FromEvent<MouseButtonEventArgs>(uiElement, "MouseLeftButtonDown");
        }

        public static IObservable<IEvent<MouseButtonEventArgs>> GetMouseLeftButtonUp(this UIElement uiElement)
        {
            return Observable.FromEvent<MouseButtonEventArgs>(uiElement, "MouseLeftButtonUp");
        }

        public static IObservable<IEvent<MouseEventArgs>> GetMouseMove(this UIElement uiElement)
        {
            return Observable.FromEvent<MouseEventArgs>(uiElement, "MouseMove");
        }

        public static IObservable<IEvent<MouseButtonEventArgs>> GetMouseClick(this UIElement uiElement)
        {
            var mbu = GetMouseLeftButtonUp(uiElement);
            var mbd = GetMouseLeftButtonDown(uiElement);
            var mm = GetMouseMove(uiElement);

            return
                from u in mbu.SkipUntil(mbd).TakeUntil(mm).Repeat()
                select u;
        }

        public static IObservable<TimeInterval<IEvent<MouseButtonEventArgs>>> GetDoubleClick(this UIElement uiElement)
        {
            return from dc in GetMouseClick(uiElement).TimeInterval()
                   .Where(click => click.Interval.TotalMilliseconds <= 250)
                   select dc;
        }
#endif
    }
}
